package com.petc.api.service.impl.base;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.petc.api.common.constants.Constants;
import com.petc.api.common.exception.enums.GlobalErrorCodeConstants;
import com.petc.api.common.pojo.CommonResult;
import com.petc.api.common.util.JwtUtils;
import com.petc.api.controller.param.base.UserLoginParam;
import com.petc.api.controller.view.base.UserLoginView;
import com.petc.api.entity.DeviceBind;
import com.petc.api.entity.LoginLog;
import com.petc.api.entity.User;
import com.petc.api.service.base.BaseService;
import com.petc.api.service.base.DeviceBindService;
import com.petc.api.service.base.LoginLogService;
import com.petc.api.service.user.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class BaseServiceImpl implements BaseService {

    @Value("${wx.mini.appid}")
    private String appId;

    @Value("${wx.mini.secret}")
    private String secret;

    @Resource
    private RestTemplate restTemplate;

    /**
     * 获取手机号
     */
    public static final String WX_GET_PHONE_NUMBER_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";

    /**
     * 获取接口调用凭据,需要此凭据解析手机号
     */
    public static final String WX_ACCESE_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";

    /*** 获取open_id接口*/
    public static final String OPEN_ID_URL = "https://api.weixin.qq.com/sns/jscode2session";

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private UserService userService;

    @Resource
    private LoginLogService loginLogService;

    @Resource
    private DeviceBindService deviceBindService;

    /**
     * 小程序授权登录
     *
     * @param view
     * @return
     */
    @Override
    @Transactional
    public CommonResult<UserLoginParam> login(UserLoginView view) {

        // 1.根据微信 appid+secret+微信授权流水号获取微信session_key 和 openid
        // 2.根据微信 appid+secret获取微信token
        // 3.根据微信token获取用户手机号
        // 3.根据手机号查询用户表是否存在
        // 4.不存在根据解密出的信息生成新用户，然后生成token存入redis返回登录成功。存在生成token存入redis返回登录成功。
        HashMap<String, Object> parse = new HashMap<>();

        try {
            //appid+secret+微信授权流水号获取微信session_key 和 openid
            getUserOpenId(parse, view.getCode());
        } catch (RuntimeException e) {
            //根据微信返回异常码定义返回异常
            return CommonResult.error(GlobalErrorCodeConstants.WX_ERROR);
        }
        //获取用户信息是否存在，存在生成登录会话返回,否则进行注册
        User user = userService.queryByUser(parse);
        if (Objects.nonNull(user)) {
            return loginToken(user, view, parse);
        }
        try {
            //appid+secret获取微信token
            getAccessToken(parse);
            //获取用户手机号
            getWxUserPhone(parse, view.getCode());
        } catch (RuntimeException e) {
            //根据微信返回异常码定义返回异常
            return CommonResult.error(GlobalErrorCodeConstants.WX_ERROR);
        }


        return loginToken(user, view, parse);
    }


    /**
     * 根据微信 appid+secret+微信授权流水号获取微信session_key 和 openid
     *
     * @param code 微信code
     * @return
     */
    private void getUserOpenId(HashMap<String, Object> parse, String code) {
        StringBuilder openIdUrl = new StringBuilder(OPEN_ID_URL);
        openIdUrl.append("?grant_type=").append("authorization_code");
        openIdUrl.append("&appid=").append(appId);
        openIdUrl.append("&secret=").append(secret);
        openIdUrl.append("&js_code=").append(code);
        ResponseEntity<String> openIdRes = restTemplate.getForEntity(openIdUrl.toString(), String.class);
        if (openIdRes.getStatusCodeValue() != 200 || Objects.isNull(openIdRes.getBody())) {
            throw new RuntimeException("OpenId接口调用异常~");
        }
        // 2. 解析微信返回数据
        HashMap<String, Object> hashMap = JSON.parseObject(openIdRes.getBody(), HashMap.class);
        if (Objects.nonNull(parse.get("errcode"))) {
            throw new RuntimeException("OpenId接口调用异常~");
        }
        parse.put("openId", hashMap.get("openid"));
        parse.put("sessionKey", hashMap.get("session_key"));
        parse.put("unionId", hashMap.get("unionid"));
    }


    /**
     * 获取Access_Token，消息订阅或者微信授权都需要这个东西，可以放入缓存因为每次获取都有两个小时有效期
     */
    private void getAccessToken(HashMap<String, Object> parse) {
        // 查询redis 是否有存放access_token避免过多次请求
        Object redisRes = redisTemplate.opsForValue().get("REDIS_KEY_ACCESS_TOKEN");
        if (Objects.nonNull(redisRes)) {
            parse.put("accessToken", redisRes);
        }
        // 请求ACCESS_TOKEN
        StringBuilder accessTokenUrl = new StringBuilder(WX_ACCESE_TOKEN_URL);
        accessTokenUrl.append("?grant_type=client_credential");
        accessTokenUrl.append("&appid=").append(appId);
        accessTokenUrl.append("&secret=").append(secret);
        ResponseEntity<HashMap> accessTokenRes = restTemplate.getForEntity(accessTokenUrl.toString(), HashMap.class);
        if (accessTokenRes.getStatusCodeValue() != 200 || Objects.isNull(accessTokenRes.getBody()) || Objects.nonNull(accessTokenRes.getBody().get("errcode"))) {
            throw new RuntimeException("Token接口调用异常~");
        }
        log.info("=== 微信手机授权请求AccessToken返回：{} ", accessTokenRes.getBody());
        String accessToken = accessTokenRes.getBody().get("access_token").toString();
        parse.put("accessToken", accessToken);
        // 存放redis 设置两个小时超时时间
        redisTemplate.opsForValue().set("REDIS_KEY_ACCESS_TOKEN", accessToken, 2L, TimeUnit.HOURS);
    }

    /**
     * 解析用户手机号
     */
    private void getWxUserPhone(HashMap<String, Object> parse, String code) {
        // 请求解析手机号
        StringBuilder getPhoneNumberUrl = new StringBuilder(WX_GET_PHONE_NUMBER_URL);
        getPhoneNumberUrl.append("?access_token=").append(parse.get("accessToken"));
        Map<String, String> phoneNumberParam = new HashMap<>();
        phoneNumberParam.put("code", code);
        ResponseEntity<HashMap> hashMapResponseEntity = restTemplate.postForEntity(getPhoneNumberUrl.toString(), phoneNumberParam, HashMap.class);
        if (hashMapResponseEntity.getStatusCodeValue() != 200 || Objects.isNull(hashMapResponseEntity.getBody()) || (Integer) hashMapResponseEntity.getBody().get("errcode") != 0) {
            throw new RuntimeException("解析手机接口调用异常~");
        }
        log.info("=== 微信手机授权请求解析手机号返回：{} ", hashMapResponseEntity.getBody());
        LinkedHashMap phoneInfo = (LinkedHashMap) hashMapResponseEntity.getBody().get("phone_info");
        String purePhoneNumber = (String) phoneInfo.get("purePhoneNumber");
        parse.put("phone", purePhoneNumber);
    }


    private CommonResult<UserLoginParam> loginToken(User user, UserLoginView view, HashMap<String, Object> parse) {
        LoginLog loginLog = new LoginLog();

        //用户第一次授权登录，保存用户信息
        if(Objects.isNull(user)) {
            user.setPhone((String) parse.get("phone"));
            user.setOpenId((String) parse.get("openId"));
            user.setUnionId((String) parse.get("unionId"));
            user.setLoginDate(LocalDateTime.now());
            userService.insert(user);
            user = userService.queryByUser(parse);
        }

        loginLog.setUserId(user.getId());
        loginLog.setPhone(user.getPhone());
        loginLog.setDeviceId(view.getDeviceId());
        loginLog.setLoginState("00000");
        loginLog.setLoginTime(LocalDateTime.now());
        loginLog.setLoginIp(view.getIp());


        try {
            //判断用户当前登录设备是否新设备
            DeviceBind deviceBind = deviceBindService.queryByUserId(user.getId());
            if (Objects.isNull(deviceBind)) {
                deviceBind.setUserId(user.getId());
                deviceBind.setDeviceId(view.getDeviceId());
                deviceBind.setDeviceName(view.getDeviceName());
                deviceBind.setBindTime(LocalDateTime.now());
                deviceBindService.insert(deviceBind);
            } else {
                //判断当前设备id是否已绑定,未绑定更新已绑定设备状态并添加新绑定设备
                if (!deviceBind.getDeviceId().equals(view.getDeviceId())) {
                    //之前绑定设备进行解除绑定
                    deviceBind.setBindState(Constants.NO);
                    deviceBindService.update(deviceBind);

                    //添加新的绑定设备
                    DeviceBind deviceBindNew = new DeviceBind();
                    deviceBind.setUserId(user.getId());
                    deviceBind.setDeviceId(view.getDeviceId());
                    deviceBind.setDeviceName(view.getDeviceName());
                    deviceBind.setBindTime(LocalDateTime.now());
                    deviceBindService.insert(deviceBindNew);
                }
            }
        } catch (Exception e) {
            log.error("登录异常", e);
            loginLog.setLoginState("99999");
            loginLogService.loginInsert(loginLog);
            return CommonResult.error(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR);
        }
        //登录日志保存
        loginLogService.loginInsert(loginLog);
        //保存登录会话
        String token = saveSession(user, view);
        UserLoginParam param = new UserLoginParam();
        param.setToken(token);
        param.setDeviceId(view.getDeviceId());
        param.setUserId(user.getId());
        return CommonResult.success(param);
    }

    /**
     * 保存登录会话
     *
     * @param user
     * @param view
     * @return
     */
    private String saveSession(User user, UserLoginView view) {
        JSONObject userInfo = new JSONObject();
        userInfo.put("userId", user.getId());
        userInfo.put("userName", user.getUserName());
        userInfo.put("phone", user.getPhone());
        userInfo.put("deviceId", view.getDeviceId());
        userInfo.put("deviceName", view.getDeviceName());
        userInfo.put("ip", view.getIp());

        Integer userId = user.getId();

        /*
        //开通手机号短信验证登录时开启互踢
        if (redisTemplate.hasKey(userId)) {
            String oldtoken = (String) redisTemplate.opsForValue().get(userId);
            if (oldtoken != null && redisTemplate.hasKey(oldtoken)) {
                //标记互踢
                redisTemplate.opsForValue().set(oldtoken, Constants.OFF_FLAG, 500L, TimeUnit.SECONDS);
            }
        }*/

        // 每次都生成最新的token存储用户信息，根据openId+userId生成token
        Map<String, Object> tokenMap = new HashMap<>();
        tokenMap.put("userId", userId);
        tokenMap.put("openId", user.getOpenId());
        String token = JwtUtils.generateToken(tokenMap);
        redisTemplate.opsForValue().set(token, userInfo.toString(), 500L, TimeUnit.SECONDS);
        redisTemplate.opsForValue().set(userId, token, 500L, TimeUnit.SECONDS);
        return token;
    }
}
